package info.pinlab.pinplayer.session;

import info.pinlab.pinplayer.cache.Cache;
import info.pinlab.pinplayer.cache.CacheManager;
import info.pinlab.pinplayer.cache.CachedValue;
import info.pinlab.pinplayer.cache.DiskCache;
import info.pinlab.pinplayer.cache.DiskCache.DiskCacheBuilder;
import info.pinlab.pinplayer.cache.MemCache;
import info.pinlab.pinplayer.cache.SimpleCacheManagerImpl.SimpleCacheManagerBuilder;
import info.pinlab.pinplayer.control.DiskEnrollController;
import info.pinlab.pinplayer.control.EnrollController;
import info.pinlab.pinplayer.io.JsonAdapterIF;
import info.pinlab.pinplayer.io.SimpleJsonAdapter;
import info.pinlab.pinplayer.model.rule.StepRule;
import info.pinlab.pinplayer.model.rule.StepRule.StepRuleBuilder;
import info.pinlab.pinplayer.model.task.EntryTask;
import info.pinlab.pinplayer.model.task.InfoTask;
import info.pinlab.pinplayer.model.task.RecordTask;
import info.pinlab.pinplayer.model.task.TaskSet;
import info.pinlab.pinplayer.session.Configurator.Key;
import info.pinlab.pinplayer.view.PlayerTopView;
import info.pinlab.pinplayer.view.swing.PlayerTopPanel;
import info.pinlab.pinsound.PlayerDeviceController;
import info.pinlab.pinsound.RecorderDeviceController;
import info.pinlab.pinsound.app.AudioPlayer;
import info.pinlab.pinsound.app.AudioRecorder;
import info.pinlab.snd.oal.OpenAlPlayer;
import info.pinlab.util.FileStringTools;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

/**
 * 
 * Handles configuration files, check storages for cache, finds sensible defaults... etc.
 * 
 * @author Gabor Pinter
 */
public class Initializer {
	static CacheManager cm ;
	static Configurator effectiveConf;
	static Map<Class<?>, Class<?>> interfaceMap; 
	static Map<String, Class<?>> tag2ClazzMap; 
	static Logger logger;
	static JsonAdapterIF jsonAdapter;
	static TaskSet tset;
	static PlayerTopView view;
	static EnrollController enroller = null;

	
	static PlayerDeviceController playerDevController;
	static AudioPlayer player;
	static RecorderDeviceController recorderDevController;
	static AudioRecorder recorder;
	
	
//	private static Map<Class<?>, Object> initObjects = new HashMap<Class<?>, Object>();
	
	private static void initLog(){
		BasicConfigurator.configure();
		logger = Logger.getLogger(Initializer.class);
	}
	

	/**
	 *  Initializes properties (or configuration object). 
	 *  
	 * @param runtimeConf
	 */
	private static void initConf(Map<Key, String> runtimeConf){
		//--  #1 distribution default
		Configurator defaultConf = Configurator.getDefaultInstance();
		effectiveConf = defaultConf;
		
		//---------------------
		//--  #2 .jar's default properties
		//-- gets defaults from the property file bundled in jar
		InputStream is = null;
		String jarConfFile = null;

		//-- check where to find dist conf file in jar
		if(runtimeConf!=null){
			jarConfFile = runtimeConf.get(Key.DIST_CONF_FILE);
			if(jarConfFile!=null){
				is = Configurator.class.getResourceAsStream(jarConfFile);
				if(is==null){
					logger.warn("No distribution conf file found at '" + jarConfFile + "'");
				}
			}
		}
		if(is==null){
			jarConfFile = Key.DIST_CONF_FILE.getDefaultValue();
			if(jarConfFile!=null){
				is = Configurator.class.getResourceAsStream(jarConfFile);
				if(is==null){
					logger.warn("No distribution conf file found at '" + jarConfFile+ "'");
				}
			}
		}
		if(is!=null){
			try {
				effectiveConf.put(is);
			}catch (IOException ignore) {	}
		}
		
		//---------------------
		//--  #3 local default
		//-- reads form local machine's defaults
		String userDir = null;
		String userConfFile = null;
		//-- first find where to look for local file!
		if(runtimeConf!=null){
			userDir = runtimeConf.get(Key.USER_DIR);
			userConfFile = runtimeConf.get(Key.USER_CONF_FILE);
		}
		if(userDir==null){
			userDir = effectiveConf.get(Key.USER_DIR);
		}
		if(userConfFile==null){
			userConfFile = effectiveConf.get(Key.USER_CONF_FILE);
		}
		if(userDir==null){
			userDir = Key.USER_DIR.getDefaultValue();
		}
		if(userConfFile==null){
			userConfFile = Key.USER_CONF_FILE.getDefaultValue();
		}
		File confFile = new File(
				System.getProperty("user.home")+ FileStringTools.SEP 
				+ userDir + FileStringTools.SEP + userConfFile
				);
		if(confFile.exists() && confFile.isFile()){
			try {
				InputStream confIs = new FileInputStream(confFile);
				Configurator localConf = Configurator.load(confIs);
				effectiveConf.put(localConf);
			} catch (FileNotFoundException e){
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		
		//---------------------
		// #4 command line
		if(runtimeConf!=null){
			effectiveConf.put(runtimeConf);
		}
	}

	
	
	private static void initClassMaps(){
		interfaceMap = new HashMap<Class<?>, Class<?>>();
		tag2ClazzMap = new HashMap<String, Class<?>>();
	}

	
	private static void initJsonAdapter(){
		boolean noRefsInJson = FileStringTools.getBoolean(
				effectiveConf.get(Key.JSON_NO_REFS)
				);
		jsonAdapter = new SimpleJsonAdapter(noRefsInJson);
	}
	
	
	/**
	 * Initializes local disk cache. 
	 * 
	 * 
	 * @return DiskCache instance, null if not local disk is available
	 */
	private static DiskCache initDiskCache( ){
//		System.out.println("Local avail: "+ effectiveConf.get(Key.LOCAL_AVAILABLE));
		if(!FileStringTools.getBoolean(effectiveConf.get(Key.LOCAL_AVAILABLE))){
			return null;
		}
		DiskCacheBuilder dcb = new DiskCacheBuilder();
		String path = null;
		path = effectiveConf.get(Key.LOCAL_DIR);
		if(path.isEmpty()){
			path = Key.LOCAL_DIR.getDefaultValue();
		}
		if(!new File(path).isAbsolute()){
			path = System.getProperty("user.home")+ FileStringTools.SEP + path;
//					+ FileStringTools.SEP+ "cache" + FileStringTools.SEP;
		}

		dcb
			.setDiskCacheRootPath(new File(path))
			.setClassInterfaceMap(interfaceMap)
			.setTag2ClassMap(tag2ClazzMap)
			.setJsonAdapter(jsonAdapter)
			;
		return dcb.build();
	}
	

	private static void initCachesAndEnrollController(){
		SimpleCacheManagerBuilder scmb = new SimpleCacheManagerBuilder();
		//-- Order is important --//
		//-- Memory Cache is always available! --//
		scmb.addCache(MemCache.getInstance(), true);

		//-- Disk cache : on request --//
		if(effectiveConf.getBoolean(Key.LOCAL_AVAILABLE)){
			Cache diskCache = initDiskCache();
			if(diskCache!=null){
				scmb.addCache(diskCache , false);
				enroller = new DiskEnrollController();
				enroller.setCache(diskCache);
			}
		}
		// -- other caches ? --//
		cm = scmb.build();
		
		CachedValue.setCacheManager(cm);
		jsonAdapter.setCacheManager(cm);
	}
	
	
	public static Configurator getEffectiveConf(){
		return effectiveConf;
	}
	
	public static JsonAdapterIF getJsonAdapter(){
		return jsonAdapter;
	}
	
	
	public static void initGui(){
		//-- Swing, or whatever --//
		view = new PlayerTopPanel();
	}
	
	

	/**
	 * Runtime loads audio library
	 */
	public static void initAudio() throws IllegalStateException{
		String playerDevLoaderClassName = effectiveConf.get(Key.AUDIO_PlAY_DEV_LOADER);
		Object playerObj = getClassForName(playerDevLoaderClassName);
		if(playerObj!=null){
			if(playerObj instanceof PlayerDeviceController){
				playerDevController = (PlayerDeviceController)playerObj;
				logger.info("Loading audio player device controller '" + playerDevLoaderClassName +"'");
				player = new AudioPlayer();
				player.setPlayerDevice(playerDevController);
			}else{
				logger.error("Not a player device controller  '" + playerObj.getClass().getName() +"'");
			}
		}else{
			logger.error("No initialize " + playerDevLoaderClassName);
		}

		String recorderDevLoaderClassName = effectiveConf.get(Key.AUDIO_REC_DEV_LOADER);
		Object recObj =  getClassForName(recorderDevLoaderClassName);
		if(recObj != null){
			if(recObj instanceof RecorderDeviceController){
				recorderDevController = (RecorderDeviceController)recObj;
				logger.info("Loaded audio recorder device controller '" + recorderDevLoaderClassName +"'");
				recorderDevController.setRecorderDevice(effectiveConf.getInt(Key.REC_DEVICE));
				recorder = new AudioRecorder();
				recorder.setRecorderDevice(recorderDevController);
			}else{
				logger.error("Not a player device controller  '" + recObj.getClass().getName() +"'");
			}
		}else{
			logger.error("No initialize " + recorderDevLoaderClassName);
		}
	}
	
	
	
	private static Object getClassForName(String clazzForName) throws IllegalStateException{
		String err = null;
		if(clazzForName==null){
			err = "No class name is given!";
			throw new IllegalStateException(err);
		}else{
			Class<?> clazz;
			try {
				clazz = Class.forName(clazzForName);
				return clazz.newInstance();
			} catch (ClassNotFoundException e) {
				err = "Can't find class  '" + clazzForName +"'";
				e.printStackTrace();
				throw new IllegalStateException(err);
			} catch (InstantiationException e) {
				err = "Can't init class '" + clazzForName +"'";
				e.printStackTrace();
				throw new IllegalStateException(err);
			} catch (IllegalAccessException e) {
				err = "Can't access class '" + clazzForName +"'";
				e.printStackTrace();
				throw new IllegalStateException(err);
			}
		}
	}
	
	
	public static void init(){
		init(null);
		
	}

	
	
	
	/**
	 * Load task set - from wherever
	 * 
	 * 1. bundled .conf file:
	 * 2.  
	 * 
	 */
	public static void initTaskSet(){
		tset = new TaskSet();
		StepRule srule = new StepRuleBuilder()
		.setNextByResp(true)
			.build();
		
		
		
		RecordTask rtask = new RecordTask();
		rtask.addDisplay("Record!");
		tset.add(rtask);

		EntryTask task = new EntryTask();
		task.addDisplay("1 entry task");
		task.setStepRule(srule);
		tset.add(task);
		
		
		EntryTask task2 = new EntryTask();
		task2.addDisplay("2 entry task");
		task2.setStepRule(srule);
		tset.add(task2);
		
		InfoTask task3 = new InfoTask("<html><p style=\"color:#2F355B;\"> First task!</p><p><b>second</b></p></html>");
		task3.addDisplay("No html");
//		task3.setStepRule(
//				new StepRuleBuilder().setTimeout(1000)
//		.build());
		tset.add(task3);

		InfoTask task4 = new InfoTask("<html><p style=\"color:#2F355B;\"> First task!</p><p><b>second</b></p></html>");
		tset.add(task4);

		
		//		tset.add(new InfoTask("Second slide!"));
//		Task task2 = new InfoTask("Second slide!");
//		tset.add(task2);
//		tset.add(task2);
//		tset.add(new InfoTask("Third slide!"));
	}
	
	public static void init(Map<Key, String> runtimeConf){
		initLog();
		initClassMaps();
		initConf(runtimeConf);
		initJsonAdapter();
		initCachesAndEnrollController();
		initTaskSet();
		initGui();
		
		initAudio();
	}
	
}
